import { CSSProperties, useEffect, useState, useRef, ReactNode } from "react";
import styles from './VirtualList.less';

const FULL_RENDER_COUNT = 50;
const DEFAULT_RENDER_BUFFER = 10;
const MAX_SCROLL_VELOCITY = 800;

type Props = {
    data: any[];
    getItemHeight: (item: any, index: number) => number;
    keyExtractor: (item: any, index: number) => string;
    renderItem: (item: any, index: number, key: string) => React.ReactNode;
    useSecondBuffer?: boolean;
    renderSecondBuffer?: (item: any, index: number, key: string) => React.ReactNode;
    Header?: ReactNode;
    Footer?: ReactNode;
    headerHeight?: number;
    footerHeight?: number;
    EmptyView?: ReactNode;
    bufferCount?: number;
    dividerHeight?: number;
    style?: CSSProperties;
    onScrollToBottom?: () => void,
    onScrollToTop?: () => void,
};

export default (props: Props) => {

    const {
        data,
        getItemHeight,
        keyExtractor,
        renderItem,
        useSecondBuffer,
        renderSecondBuffer,
        Header,
        Footer,
        headerHeight = 0,
        footerHeight = 0,
        EmptyView,
        dividerHeight = 0,
        bufferCount = DEFAULT_RENDER_BUFFER,
        style,
        onScrollToBottom,
        onScrollToTop,
    } = props;

    const scrollViewRef = useRef<any>(null);
    const scrollViewHeight = useRef<number>(window.innerHeight);

    const [scrollDistance, setScrollDistance] = useState<number>(0);
    const [deltaY, setDeltaY] = useState<number>(0);

    const [prefixSumArray, setPrefixSumArray] = useState<number[]>([]);

    useEffect(() => {
        scrollViewHeight.current = scrollViewRef.current?.offsetHeight || window.innerHeight;
        scrollViewRef.current?.addEventListener('scroll', onScroll);
        return () => {
            scrollViewRef.current?.removeEventListener('scroll', onScroll);
        };
    }, [scrollViewRef.current]);

    useEffect(() => {
        const heightArray = data.map((item, index) => {
            if (index == 0 && !!Header) {
                return getItemHeight(item, index) + dividerHeight + headerHeight;
            }
            if (index == data.length - 1 && !!Footer) {
                return getItemHeight(item, index) + dividerHeight + footerHeight;
            }
            return getItemHeight(item, index) + dividerHeight;
        });
        const prefixSum = buildPrefixSum(heightArray);
        setPrefixSumArray(prefixSum);
        handleScroll(scrollDistance || 0, 0);
    }, [data]);

    const onScroll = () => {
        const div = scrollViewRef.current;
        const scrollTop = div.scrollTop;
        const deltaY = scrollTop - scrollDistance;
        handleScroll(scrollTop, deltaY);
    }

    const handleScroll = (scrollTop: number, deltaY: number) => {
        const div = scrollViewRef.current;
        if (!div) {
            return;
        };
        setScrollDistance(scrollTop);
        setDeltaY(deltaY);
    }

    // 实时计算渲染行索引范围
    const calculateIndexRange = (scrollDistance: number, renderBuffer: number[]): number[] => {
        if (!data?.length) {
            return [0, 0];
        }
        if (data.length <= FULL_RENDER_COUNT) {
            return [0, data?.length - 1];
        }
        let startIndex = sideBinarySearch(prefixSumArray, scrollDistance);
        let endIndex = sideBinarySearch(prefixSumArray, scrollDistance + scrollViewHeight.current);
        
        if (startIndex < 0) startIndex = 0;
        if (endIndex > data.length - 1) endIndex = data.length - 1;
        if (endIndex < startIndex) endIndex = startIndex;

        let bufferStartIndex = startIndex - renderBuffer[0];
        let bufferEndIndex = endIndex + renderBuffer[1];
        if (bufferStartIndex < 0) bufferStartIndex = 0;
        if (bufferEndIndex > data.length - 1) bufferEndIndex = data.length - 1;

        return [bufferStartIndex, bufferEndIndex, startIndex, endIndex];
    }

    // 是否位于二级缓冲
    const isSecondBuffer = (index: number): boolean => {
        if (data.length <= FULL_RENDER_COUNT) {
            return false;
        }
        const [bufferStart, bufferEnd, start, end] = indexRange;

        const originIndex = index + bufferStart;
        const headBuffer = start - bufferStart;
        const tailBuffer = bufferEnd - end;
        return (originIndex >= bufferStart && originIndex < start - headBuffer / 2) 
            || (originIndex <= bufferEnd && originIndex > end + tailBuffer / 2);
    }

    const getRenderListData = (indexRange: number[]) => {
        if (data.length <= FULL_RENDER_COUNT) {
            return data;
        }
        return data.slice(indexRange[0], indexRange[1] + 1);
    };

    const activation = Math.floor(bufferCount * Math.tanh(Math.abs(deltaY) / (MAX_SCROLL_VELOCITY / 2)));
    const renderBuffer = deltaY >= 0 
        ? [bufferCount + activation, bufferCount - activation]
        : [bufferCount - activation, bufferCount + activation];

    const indexRange = calculateIndexRange(scrollDistance, renderBuffer);

    const [bufferStart, bufferEnd] = indexRange;
    const preBlank = prefixSumArray[bufferStart - 1] || 0;
    const afterBlank = (prefixSumArray[prefixSumArray.length - 1] - prefixSumArray[bufferEnd]) || 0;

    const renderListData = getRenderListData(indexRange);

    if (!data?.length) {
        return (EmptyView || <></>);
    }

    return (
        <div className={styles.container} style={style || {}} ref={scrollViewRef}>
            {!!Header && Header}
            {/* 前占位 */}
            <div style={{ height: 0, marginTop: preBlank }}/>
            {renderListData?.map((item: any, index: number) => {
                const originIndex = index + indexRange[0];
                const inSecondBuffer = isSecondBuffer(index);
                if (useSecondBuffer && inSecondBuffer) {
                    if (renderSecondBuffer) {
                        return renderSecondBuffer(item, originIndex, keyExtractor(item, originIndex));
                    } else {
                        return (
                            <div
                                style={{ minHeight: getItemHeight(item, originIndex), marginTop: dividerHeight || 0, backgroundColor: 'white' }}
                                key={keyExtractor(item, originIndex)}
                            />
                        );
                    }
                }
                return renderItem(item, originIndex, keyExtractor(item, originIndex));
            })}
            {/* 后占位 */}
            <div style={{ height: 0, marginBottom: afterBlank }}/>
            {!!Footer && Footer}
        </div>
    );
}

// 生成前缀和数组
const buildPrefixSum = (heightArray: number[]): number[] => {
    const t1 = performance.now();
    if (!heightArray?.length) return [];
    const prefixSum: number[] = [heightArray[0]];
    for (let i = 1; i < heightArray.length; i++) {
        prefixSum[i] = prefixSum[i - 1] + heightArray[i];
    }
    const t2 = performance.now();
    console.log(`## 构造前缀和数组耗时=${t2 - t1}毫秒`);
    return prefixSum;
}

// 偏置二分查找
const sideBinarySearch = (arr: number[], m: number): number => {
    if (!arr?.length) {
        return -1;
    }
    let low = 0;
    let high = arr.length - 1;
    if (arr[low] >= m) {
        return low;
    }
    if (arr[high] <= m) {
        return high;
    }

    let result = -1;
    while (low <= high && arr[low] <= m && arr[high] > m) {
        // 计算可能的位置，利用数组的均匀性进行插值
        const pos = low + Math.floor((high - low) * (m - arr[low]) / (arr[high] - arr[low]));
        if (arr[pos] == m) {
            result = pos;
            break;
        }
        if (arr[pos] > m) {
            result = pos; // 更新结果为当前可能的候选
            high = pos - 1; // 继续在左半部分查找是否有更小的索引
        } else if (arr[pos] < m) {
            low = pos + 1; // 继续在右半部分查找
        } else {
            // 如果找到等于 m 的元素，继续向右查找第一个大于 m 的元素
            low = pos + 1;
        }
    }

    // 如果找到大于 m 的元素，返回其索引；否则返回 -1
    if (result !== -1) {
        return result;
    } else if (low < arr.length && arr[low] > m) {
        return low;
    } else {
        return -1;
    }
}